import pygame
import enum
from signal_slot import Signal



class PlayMode(enum.Enum):
    ONCE = 0        # 只播放一次
    REPEAT = 1      # 循环播放
    PINGPONG = 2    # 往复循环播放



class FramesSet:
    """
    精灵帧集合，用于AnimatedSprite类。
    """
    
    def __init__(self, surfaces: list[pygame.Surface], name: str = "default", frames: int = 5) -> None:
        self.surfaces: list = surfaces
        self.name: str = name
        self.frames: int = frames
        self.play_mode: PlayMode = PlayMode.ONCE
        self.__curr_index: int = 0
        self.__start_right_pp: bool = False
        self.__is_end: bool = False

    def __getitem__(self, index):
        if isinstance(index, slice):
            return [self.surfaces[i] for i in range(*index.indices(len(self.surfaces)))]
        elif isinstance(index, int):
            return self.surfaces[index]
        else:
            raise TypeError("invalid argument type")

    def next(self) -> pygame.Surface:
        if self.__curr_index >= len(self.surfaces):
            if self.play_mode == PlayMode.ONCE:
                self.__curr_index = len(self.surfaces) - 1
            elif self.play_mode == PlayMode.REPEAT:
                self.__curr_index = 0
            self.__is_end = True
        else:
            self.__is_end = False

        surface = self.surfaces[self.__curr_index]

        if self.play_mode == PlayMode.PINGPONG :
            if self.__curr_index == len(self.surfaces) - 1:
                self.__start_right_pp = True
            elif self.__curr_index == 0:
                self.__start_right_pp = False
                self.__is_end = True

            if self.__start_right_pp:
                self.__curr_index -= 1
            else:
                self.__curr_index +=1
                self.__is_end = False
        else:
            self.__curr_index += 1
        return surface
    
    def is_end(self) -> bool:
        return self.__is_end
    
    def one_frame_duration(self) -> int:
        return round(1000.0 / self.frames)
    
    

class AnimatedSprite(pygame.sprite.Sprite):
    """
    包含多个surface作为动画播放帧的pygame.sprite.Sprite子类，
    使用FramesSet创建精灵帧集合。
    """

    def __init__(self, *groups) -> None:
        super().__init__(*groups)
        self.__frames_set_dict: dict[str, FramesSet] = {}
        self.__curr_animated_set: FramesSet = None
        self.__last_ticks: int = 0
        self.__playing: bool = False
        self.image = None
        self.rect = pygame.Rect(0, 0, 0, 0)

        self.animation_changed_signal = Signal()
        self.animation_finished_signal = Signal()

    def update(self, *args, **kwargs) -> None:
        if self.__curr_animated_set is None:
            return
        
        if not self.__playing: 
            return
        
        if pygame.time.get_ticks() - self.__last_ticks >= self.__curr_animated_set.one_frame_duration():
            self.image = self.__curr_animated_set.next()
            self.rect.size = self.image.get_size()
            self.__last_ticks = pygame.time.get_ticks()

            if self.__curr_animated_set.is_end():
                self.animation_finished_signal.emit()

    def draw(self, surface: pygame.Surface, *args, **kwargs) -> None:
        surface.blit(self.image, self.rect)

    def add_frames_set(self, frames_set: FramesSet) -> None:
        self.__frames_set_dict[frames_set.name] = frames_set

    def add_frames_set_from_dict(self, data: dict[str, FramesSet]) -> None:
        self.__frames_set_dict.update(data)

    def play(self, name: str) -> None:
        frames_set = self.__frames_set_dict.get(name)
        if frames_set is None:
            raise ValueError("this name is invalid.")
        if not self.__curr_animated_set == frames_set:
            self.__curr_animated_set = frames_set
            self.image = self.__curr_animated_set.next()
            self.animation_changed_signal.emit()
        else:
            if not self.__playing:
                self.__playing = True
        self.__playing = True
    
    def pause(self) -> None:
        self.__playing = False

    def get_current_animtion_name(self) -> str:
        if self.__curr_animated_set is None:
            raise Exception("not found any frames set")
        return self.__curr_animated_set.name